/*
	Colored Trails
	
	Copyright (C) 2006-2007, President and Fellows of Harvard College.  All Rights Reserved.
	
	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	as published by the Free Software Foundation; either version 2
	of the License, or (at your option) any later version.
	
	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.
	
	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

package edu.harvard.eecs.airg.coloredtrails.shared.types;

import javax.swing.*;
import java.util.List;
import java.util.ArrayList;
import java.util.Observable;
import java.io.Serializable;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

/**
 * Phases dictates the phases of the game.
 */
//TODO: When getting the name of a non-existant GamePhase (like null) should we return null or ""?
//TODO: Clean up interface between UI and API.
public class Phases extends Observable implements ActionListener, Serializable {
	
	/**
	 * the sequence of game phases
	 */
	protected List<GamePhase> phaseSequence;

	/**
	 * the previous phase
	 */
	private GamePhase previous;

	/**
	 * the current phase
	 */
	private GamePhase current;

	/**
	 * number of phases elapsed
	 */
	private int phasesElapsed = 0;

	/**
	 * flag indicating whether the phase sequence loops or not
	 */
	protected boolean isLoop = false;

	/**
	 * The timer used to count the seconds of phases.
	 */
	public static final Timer tm = new Timer(1000, null);
	static { tm.start(); }
	

	/**
	 * The number of seconds that have elapsed in the current phase.
	 */
	protected int currentelapsed;

	/**
	 * The number of seconds left in the current phase.
	 */
	protected int currentleft;
	
	public long time = System.currentTimeMillis();

	public boolean isStarted() {
		return tm.isRunning();
	}

	/* **************************************************************
		 *
		 * Constructors
		 *
		 */

	/**
	 * Constructor with no phases.
	 */
	protected Phases() {
		phaseSequence = new ArrayList<GamePhase>();
		previous = null;
		current = null;

		currentelapsed = 0;
		currentleft    = 0;

		tm.addActionListener(this);
	}

	/**
	 * Coppy constructor
	 * @param p The Phase to be coppied.
	 */
	public Phases( Phases p ) {
		this();

		phasesElapsed = p.phasesElapsed;
		isLoop = p.isLoop;

		for( GamePhase h : p.phaseSequence ) {
			GamePhase g = new GamePhase( h );
			if( p.current == h )
				current = g;
			else if( p.previous == h )
				previous = g;
			phaseSequence.add( g );
		}

		this.currentelapsed = p.currentelapsed;
		this.currentleft    = p.currentleft;
		
		this.time = p.time;
	}
	
	private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
		 out.writeObject( phaseSequence );
		 out.writeObject( previous );
		 out.writeObject( current );
		 out.writeInt(phasesElapsed);
		 out.writeBoolean(isLoop);
		 out.writeInt(currentelapsed);
		 out.writeInt(currentleft);
		 out.writeLong(time);
	 }
	 
	private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException {
		phaseSequence = (List<GamePhase>) in.readObject();
		previous = (GamePhase) in.readObject();
		current = (GamePhase) in.readObject();

		if( previous != null )
			previous = this.getPhase(previous.getName());

		if( current != null )
			current = this.getPhase(current.getName());
		
		phasesElapsed = in.readInt();
		isLoop = in.readBoolean();
		currentelapsed = in.readInt();
		currentleft = in.readInt();
		time = in.readLong();
	}
	
	public void finalize() throws Throwable {
	    tm.removeActionListener(this);
	    super.finalize();
	}

	/* **************************************************************
		 *
		 * Basic Phases methods
		 *
		 */

	/**
	 * Gets the index of the specified phase in the phase sequence
	 *
	 * @param phaseName Name of the phase
	 * @return Index of the phase
	 */
	public int getPhaseIndex( String phaseName ) {
		GamePhase gp = getPhase( phaseName );
		int i = phaseSequence.indexOf( gp );
		if( 0 <= i )
			return i;

		throw new Error( "Phases.getPhaseIndex(): unrecognized phase name " + phaseName );
	}

	/**
	 * Returns true if the parameter is a phase name, false otherwise
	 *
	 * @param phaseName Name to be checked
	 * @return phaseName is a phase name or not
	 */
	public boolean contains( String phaseName ) {
		return getPhase( phaseName ) != null;
	}

	/**
	 * Returns the number of phases in phase sequence
	 *
	 * @return Number of phases
	 */
	public int getNumPhases() {
		return phaseSequence.size();
	}

	/**
	 * Returns true if the phase loops, false otherwise
	 *
	 * @return true if the phase loops, false otherwise
	 */
	public boolean isLoop() {
		return isLoop;
	}

	/**
	 * Gets the number of phases elapsed
	 *
	 * @return the number of phases elapsed.
	 */
	public int getPhasesElapsed() {
		return phasesElapsed;
	}

	public String toString() {
		String str = "Phases: " + this.hashCode() + "\n";
		for( GamePhase g : phaseSequence )
			str += g.getName() + " " + g.hashCode() + ",  ";
		return str;
	}

	/* **************************************************************
		 *
		 * Methods for advancing phases.
		 *
		 */

	/**
	 * Ends the current game phase and advances the
	 * game to the specified game phase
	 *
	 * @param phaseName the name of hte phase.
	 */
	public void advanceToPhase( String phaseName ) {
		advanceToPhase( getPhase( phaseName ) );
	}

	private synchronized void advanceToPhase( GamePhase phase ) {
		if( phase == null ) {
			if( current != null ) {
				endPhase();                // end the current phase before moving to next phase
			}
			previous = current;
			current = null;
			return;
		}

		if( current != null ) {
			endPhase();                // end the current phase before moving to next phase
		}

		previous = current;                   // note the phase that is ending
		current = phase;

		currentleft = current.getDuration();
		currentelapsed = 0;

		time = System.currentTimeMillis();
		setChanged();
		notifyObservers( "PHASE_ADVANCED" );
		clearChanged();
		
		beginPhase();            // eventually invokes game configuration class
	}


	/**
	 * Ends the current game phase and advances the
	 * game to the next phase in the Phases sequence
	 * (may be invoked manually in configuration class
	 * or automatically by a phase time out)
	 */
	public synchronized void advancePhase() {
		GamePhase nextPhase = getNextPhase();
		advanceToPhase( nextPhase );
	}

	/**
	 * Ends the phase; eventually invokes endPhase() in game configuration class
	 */
	protected void endPhase() {
		++phasesElapsed;
	}

	/**
	 * Begins the phase; eventually invokes the beginPhase() in game configuration class
	 */
	protected void beginPhase() { }

	/* ****************************************************************
		 *
		 * Methods for getting GamePhases and GamePhase names.
		 *
		 */

	/**
	 * Returns the game phase with the specified name
	 * or null if specified name not found
	 *
	 * @param phaseName the name of the phase.
	 * @return the game phase with name phaseName, or null if it was not found.
	 */
	protected GamePhase getPhase( String phaseName ) {
		for( GamePhase gp : phaseSequence ) {
			if( gp.getName().equals( phaseName ) )
				return gp;
		}
		return null;
	}

	public GamePhase getCurrentPhase() {
		return current;
	}

	/**
	 * Gets the name of the current phase
	 *
	 * @return Name of the current phase
	 */
	public String getCurrentPhaseName() {
		//TODO: this should probably return null instead of "" when (current == null)
		return ( current == null ) ? "" : current.getName();
	}

	/**
	 * Gets the next Phase
	 *
	 * @return the next Phase in the list of Phases.  If the current Phase is null then the first Phase in the list
	 *         is returned.  If the Phase is the last Phase and there are no loops then null is returned, otherwise the
	 *         first Phase in the list is returned.
	 */
	public GamePhase getNextPhase() {
		return getNextPhase( current );
	}

	public String getNextPhaseName() {
		return getNextPhase().getName();
	}

	/**
	 * Returns the name of the phase that succeeds the specified phase
	 * in the Phases sequence
	 *
	 * @param gp phase whose successor we want
	 * @return Name of the next phase
	 */
	public GamePhase getNextPhase( GamePhase gp ) {
		if( gp == null ) {
			return phaseSequence.get( 0 );
		} else if( getLastPhase() == gp ) {
			if( !isLoop )
				return null;
			return phaseSequence.get( 0 );
		}
		return phaseSequence.get( phaseSequence.indexOf( gp ) + 1 );
	}

	public String getNextPhaseName( GamePhase gp ) {
		return getNextPhase( gp ).getName();	
	}

	protected GamePhase getPreviousPhase() {
		return previous;
	}

	/**
	 * Gets the name of the previous phase that actually executed
	 * (NOTE that the phase that actually preceeded the current phase
	 * may have been something other than what preceeds the current
	 * phase in the Phases sequence; this is because we can manually
	 * jump arbitrarily between phases)
	 *
	 * @return Name of the previous phase
	 */
	public String getPreviousPhaseName() {
		return previous == null ? "" : previous.getName();
	}

	/**
	 * Gets the name of the phase that appears before the specified
	 * phase in the Phases sequence
	 *
	 * @param gp GamePhase to be referenced
	 * @return Name of the previous phase
	 */
	public GamePhase getPreviousPhase( GamePhase gp ) {
		if( gp == null ) return null;

		if( gp == phaseSequence.get(0) )
			if( isLoop )
				return getLastPhase();
			else
				return null;
		else
			return phaseSequence.get( phaseSequence.indexOf( gp ) - 1 );
	}

	/**
	 * Gets the name of the phase that appears before the specified
	 * phase in the Phases sequence
	 *
	 * @param refPhaseName Name of the phase to be referenced
	 * @return Name of the previous phase
	 */
	public String getPreviousPhaseName( String refPhaseName ) {
		if( refPhaseName.equals( "" ) ) return "";

		GamePhase gp = getPreviousPhase( getPhase( refPhaseName ) );
		if( gp == null ) return "";
		return gp.getName();
	}

	/**
	 * Gets the last phase of the phase sequence.
	 *
	 * @return The last phase of the phase sequence.
	 */
	protected GamePhase getLastPhase() {
		return phaseSequence.get( phaseSequence.size() - 1 );
	}

	/**
	 * Gets the name of the last phase of the phase sequence.
	 *
	 * @return The last phase of the phase sequence.
	 */
	public String getLastPhaseName() {
		return getLastPhase().getName();
	}

	/* **************************************************************
		 *
		 * Methods for dealing with the timing of phases.
		 *
		 */
	
  /**
   * Called when an action e is performed
   */
	public void actionPerformed( ActionEvent e){
	    ++currentelapsed;
	    	    
		if( (current != null) && (! current.isIndefinite()) ) {
			--currentleft;
			
			// if game phase is not indefinite, and the decremented time is <= 0 ...
			if( currentleft <= 0 ) {
				advancePhase();
			}
		}
	}

	/**
	 * Gets seconds left on the current phase
	 *
	 * @return Seconds left on the  current phase
	 */
	public int getCurrentSecsLeft() {
		return currentleft;
	}

	/**
	 * Gets seconds elapsed on the current phase
	 *
	 * @return Seconds elapsed on the current phase
	 */
	public int getCurrentSecsElapsed() {
		return currentelapsed;
	}

	/**
	 * Gets the duration of the specified phase
	 *
	 * @param phaseName Name of the phase whose duration we want
	 * @return Duration of the phase
	 */
	public int getPhaseDuration( String phaseName ) {
		if( phaseName.equals( "" ) )
			return 0;

		GamePhase gp = getPhase( phaseName );
		if( gp != null )
				return gp.getDuration();

		throw new Error( "Phases.getPhaseDuration(): unrecognized phase name " + phaseName );
	}

	/**
	 * Gets the duration of the current phase
	 *
	 * @return Duration of the current phase
	 */
	public int getPhaseDuration() {
		return getPhaseDuration( current.getName() );
	}
}